#!/usr/bin/python
#coding:utf-8
'''
第一章  神经元/感知器
博客地址：https://www.zybuluo.com/hanbingtao/note/433855
代码地址：https://github.com/hanbt/learn_dl/blob/master/perceptron.py
注释地址：https://github.com/chenyyx/deeplearning-notes/blob/master/src/lala/perceptron.py
'''
class Perceptron(object):
    '''
    感知器类
    '''
    def __init__(self,input_num,activator):
        '''
        Desc：
            初始化感知器
        Args：
            input_num:设置输入参数的个数
            activator:以及激活函数
        Returns：
            None
        激活函数的类型为double -> double
        '''
        #设置激活函数
        self.activator = activator
        #权重向量初始化0
        self.weights = [0.0 for _ in range(input_num)]
        #偏置项初始化为0
        self.bias = 0.0
    
    def __str__(self):
        '''
        打印学习到的权重、偏置项
        '''
        return 'weights\t:%s\nbias\t:%f\n' % (self.weights,self.bias)
    
    def predict(self,input_vec):
        '''
        Desc:
            输入向量，输出感知器的计算结果
        Args:
            input_vec：输入向量
        Returns:
            感知器的计算结果
        '''
        #把input_vec[x1,x2,x3...]和weights[w1,w2,w3...]打包在一起
        #变成[(x1,w1),(x2,w2),(x3,w3)...]
        #利用map函数计算[x1*w1,x2*w2,x3*w3]
        #最后利用reduce求和，
        #reduce()是python2的内置函数，从python3开始移动到functools模块
        #reduce()从左到右对一个序列的项累计地对应拥有两个参数的函数，以此合并序列到一个单一值，如reduce(lambda x,y:x+y,[1,2,3,4,5]) 计算就是((((1+2)+3)+4)+5)
        #map()接收一个函数f和list，通过把函数f依次作用在list的每个元素上，得到一个新的list返回，如f函数是计算平方，map(f,[1,2,3,4,5]) ==> 返回 [1,4,9,16,25]
        #zip() 接收任意多个（包括0个和1个）序列作为参数，返回一个tuple列表，例：x=[1,2,3] y= [4,5,6] z=[7,8,9] xyz=zip(x,y,z)  ==> [(1,4,7),(2,5,8),(3,6,9)]
        return self.activator(
            reduce(lambda a,b:a+b,
                map(lambda(x,w):x*w,
                    zip(input_vec,self.weights))
                    ,0.0)+self.bias
        )
    
    def train(self,input_vecs,labels,iteration,rate):
        '''
        Desc：
            输入训练数据：一组向量、与每个向量对应的label，以及训练轮数，学习率
        Args：
            input_vec:输入向量
            labels:数据对应标签
            iteration：训练的迭代轮数
            rate：学习率
        Return：
            none
        '''
        for i in range(iteration):
            self._one_iteration(input_vecs,labels,rate)
    
    def _one_iteration(self,input_vecs,labels,rate):
        '''
        Desc：
            一次迭代，把所有的训练数据过一遍
        Args：
            input_vecs:输入向量
            labels：数据对应的标签
            rate：学习率
        Return:
            none
        '''
        #把输入和输出打包在一起，成为样本列表[(input_vec,label),...]
        #而每个训练样本是(input_vec,label)
        samples = zip(input_vecs,labels)
        #对每个样本，按照感知器规则更新权重
        for(input_vec,label) in samples:
            #计算感知器正在当前权重下的输出
            output = self.predict(input_vec)
            #更新权重
            self._update_weights(input_vec,output,label,rate)
    
    def _update_weights(self,input_vec,output,label,rate):
        '''
        Desc:
            按照感知器规则更新权重
        Args:
            input_vec：输入向量
            output：经过感知器规则计算得到的输出
            label：输入向量对应的标签
            rate：学习率
        Return：
            none
        '''
        #把input_vec[x1,x2,x3...]和weights[w1,w2,w3...]打包一起
        #变成[(x1,w1),(x2,w2),(x3,w3)...]
        #利用感知器规则更新权重
        delta = label - output
        #map() 接收一个函数 f和一个list，并通过函数f依次作用在list的每个元素上，得到一个新的list返回
        #zip() 接收任意多个（包括0个和1个）序列作为参数，返回一个tuple列表。
        self.weights=map(
            lambda(x,w):w + rate * delta *x,zip(input_vec,self.weights)
        )
        self.bias+= rate * delta

def f(x):
    '''
    Desc：
        定义激活函数f
    Args：
        x：输入向量
    Return：
        （实现阶跃函数）大于0返回1，否则返回0
    '''
    return 1 if x>0 else 0

def get_training_dataset():
    '''
    Desc:
        基于and真值表结构训练数据
    Args:
        None
    Return:
        input_vecs:输入向量
        labels:输入向量对应的标签
    '''
    #构建训练数据
    #输入向量列表
    input_vecs= [[1,1],[0,0],[1,0],[0,1]]
    #期望的输出列表，注意要与输入一一对应
    #[1,1] ->1,[0,0] -> 0 ,[1,0] -> 0,[0,1] -> 0
    labels = [1,0,0,0]
    return input_vecs,labels

def train_and_perceptron():
    '''
    Desc:
        使用and真值表训练感知器
    Return:
        p:返回训练好的感知器
    '''
    #创建感知器，输入参数个数为2（因为and是二元函数），激活函数为f
    p = Perceptron(2,f)
    #训练，地带10轮，学习速率为0,。1
    input_vecs,labels = get_training_dataset()
    p.train(input_vecs,labels,10,0.1)
    #返回训练好的感知器
    return p

if __name__ == '__main__':
    #训练and感知器
    and_perception = train_and_perceptron()
    #打印训练获得的权重
    print and_perception

    #测试
    print '1 and 1 = %d' % and_perception.predict([1,1])
    print '0 and 0 = %d' % and_perception.predict([0,0])
    print '1 and 0 = %d' % and_perception.predict([1,0])
    print '0 and 1 = %d' % and_perception.predict([0,1])
